package com.panopset.gui.container;

import static com.panopset.Util.MONOSPACE_PLAIN_10;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.SortedSet;
import java.util.Stack;
import java.util.TreeSet;
import java.util.Map.Entry;

import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;

import com.panopset.centralen.util.MathUtil;
import com.panopset.gui.TPanel;

/**
 *
 * Drop down selection.
 *
 * @param <T>
 *            Any serializable object, make sure you override toString because
 *            that is where the menu item for this object will get it's text.
 *
 * @author Karl Dinwiddie
 *
 */
public abstract class Dropper<T extends Serializable> extends PanField<TPanel> {

    /**
     *
     * @param title
     *            Title.
     * @param defaultValue
     *            Default value.
     * @param tip
     *            Tip.
     */
    public Dropper(final String title, final String defaultValue,
            final String tip) {
        super(title, tip, defaultValue);
    }

    /**
     * Entry point.
     *
     * @param args
     *            test, no args.
     */
    public static void main(final String... args) {
        JFrame f = new JFrame("testing...");
        f.setLocation(MathUtil.ONEHUNDRED, MathUtil.ONEHUNDRED);
        f.setSize(MathUtil.ONEHUNDRED * MathUtil.SIX,
                MathUtil.ONEHUNDRED * MathUtil.THREE);
        Dropper<JLabel> d = new Dropper<JLabel>("border title", "b",
                "dropper tip") {

            @Override
            public String getMenuName(final String key, final int count) {
                return "";
            }

            /**
             * Selection list.
             */
            private Map<String, JLabel> sl;

            @Override
            public Map<String, JLabel> getSelectionList() {
                if (sl == null) {
                    sl = new HashMap<String, JLabel>();
                    JLabel lbl = new JLabel("albl") {
                        /**
                         * Serial version id.
                         */
                        private static final long serialVersionUID = 1L;

                        @Override
                        public String toString() {
                            return "astr";
                        }
                    };
                    lbl.setToolTipText("attt");
                    sl.put("a", lbl);
                    sl.put("b", lbl);
                }
                return sl;
            }

            @Override
            public String getSelectionTipText(final JLabel selection) {
                return selection.getToolTipText();
            }

            @Override
            public String getTopMenuName() {
                return "zongo the top menu";
            }

            @Override
            protected boolean isValid(final String valueToCheck) {
                return true;
            }

        };
        f.add(d.getPanel());
        f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        f.setVisible(true);
    }

    @Override
    public final String getComponentValue() {
        return getSelectionLbl().getText();
    }

    @Override
    public final void setComponentValue(final String newValue) {
        getSelectionLbl().setText(newValue);
    }

    /**
     * @return Top menu name.
     */
    public abstract String getTopMenuName();

    /**
     * If a key is to appear in a sub-menu, use the key and the count to specify
     * the name of the sub-menu.
     *
     * @param key
     *            Key of the menu item.
     * @param count
     *            Current count.
     * @return If blank is returned, the key will be added to the top menu.
     */
    public abstract String getMenuName(String key, int count);

    /**
     * @return Selection list.
     */
    public abstract Map<String, T> getSelectionList();

    /**
     * @param selection
     *            Selection.
     * @return Selection tool tip text.
     */
    public abstract String getSelectionTipText(T selection);

    /**
     * Action listener.
     */
    private ActionListener listener;

    /**
     * Menu bar.
     */
    private JMenuBar menuBar;

    @Override
    protected final TPanel createComponent() {
        TPanel p = new TPanel();
        p.add(getSelectorPnl());
        p.add(getSelectionPnl());
        return p;
    }

    /**
     * Alert new selection.
     *
     * @param o
     *            Newly selected item.
     */
    protected void alertNewSelection(final T o) {
        // Override to do something when the user makes a selection.
    }

    /**
     * @return Action listener.
     */
    private ActionListener getListener() {
        if (this.listener == null) {
            this.listener = new ActionListener() {

                @SuppressWarnings("unchecked")
                @Override
                public void actionPerformed(final ActionEvent e) {
                    if (e.getSource() instanceof StorageMenuItem) {
                        StorageMenuItem<T> item = (StorageMenuItem<T>) e
                                .getSource();
                        getSelectionLbl().setText(item.getText());
                        alertNewSelection(item.itemStorage);
                    }
                }

            };
        }
        return this.listener;
    }

    /**
     * @return Menu bar.
     */
    private JMenuBar getMenuBar() {
        if (this.menuBar == null) {
            this.menuBar = new JMenuBar();
        }
        return this.menuBar;
    }

    /**
     * Refresh.
     */
    public final void refresh() {
        getMenuBar().removeAll();
        Map<String, Stack<StorageMenuItem<T>>> menus
            = new HashMap<String, Stack<StorageMenuItem<T>>>();
        int count = 0;
        for (String key : getSelectionList().keySet()) {
            final T val = getSelectionList().get(key);
            String menuName = getMenuName(key, count++);
            Stack<StorageMenuItem<T>> menuStack = menus.get(menuName);
            if (menuStack == null) {
                menuStack = new Stack<StorageMenuItem<T>>();
                menus.put(menuName, menuStack);
            }
            StorageMenuItem<T> mi = new StorageMenuItem<T>(val);
            mi.setFont(MONOSPACE_PLAIN_10);
            mi.setToolTipText(getSelectionTipText(getSelectionList().get(key)));
            mi.addActionListener(getListener());
            menuStack.push(mi);
        }
        JMenu valueMenu = new JMenu(getTopMenuName());
        valueMenu.setFont(MONOSPACE_PLAIN_10);
        getMenuBar().add(valueMenu);
        if (menus.size() >= 1) {
            if (menus.size() == 1) {
                for (Entry<String, Stack<StorageMenuItem<T>>> entry : menus
                        .entrySet()) {
                    sortAndPopulate(valueMenu, entry.getValue());
                }
            } else {
                SortedSet<StorageMenuItem<JMenu>> set = Collections
                        .synchronizedSortedSet(
                            new TreeSet<StorageMenuItem<JMenu>>());
                for (Entry<String, Stack<StorageMenuItem<T>>> entry : menus
                        .entrySet()) {
                    JMenu subMenu = new JMenu(entry.getKey());
                    subMenu.setFont(MONOSPACE_PLAIN_10);
                    sortAndPopulate(subMenu, entry.getValue());
                    set.add(new StorageMenuItem<JMenu>(subMenu));
                }
                for (StorageMenuItem<JMenu> mi : set) {
                    valueMenu.add(mi.itemStorage);
                }
            }
        }
        getInnerComponent().invalidate();
        reset();
    }

    /**
     * Sort and populate.
     *
     * @param menu
     *            Menu to add sorted stack to.
     * @param stack
     *            Stack to sort.
     */
    private void sortAndPopulate(final JMenu menu,
            final Stack<StorageMenuItem<T>> stack) {
        sortAndPopulateArray(menu, stack);
    }

    /**
     * Sort and populate.
     *
     * @param menu
     *            Menu to add sorted stack to.
     * @param stack
     *            Stack to sort.
     */
    private void sortAndPopulateArray(final JMenu menu,
            final Stack<StorageMenuItem<T>> stack) {
        ArrayList<StorageMenuItem<T>> al = new ArrayList<StorageMenuItem<T>>();
        while (!stack.isEmpty()) {
            al.add(stack.pop());
        }
        Collections.sort(al);
        for (StorageMenuItem<T> mi : al) {
            menu.add(mi);
        }
    }

    /**
     * Sort and populate set, no duplicates allowed.
     *
     * Not used, keeping it here in case it is needed.
     *
     * @param menu
     *            Menu to add sorted stack to.
     * @param stack
     *            Stack to sort.
     */
    final void sortAndPopulateSet(final JMenu menu,
            final Stack<StorageMenuItem<T>> stack) {
        SortedSet<StorageMenuItem<T>> set = Collections
                .synchronizedSortedSet(new TreeSet<StorageMenuItem<T>>());

        while (!stack.isEmpty()) {
            set.add(stack.pop());
        }

        for (StorageMenuItem<T> mi : set) {
            menu.add(mi);
        }
    }

    /**
     * Selector panel.
     */
    private TPanel selectorPnl;

    /**
     * @return Selector panel.
     */
    private TPanel getSelectorPnl() {
        if (this.selectorPnl == null) {
            this.selectorPnl = new TPanel();
            refresh();
            this.selectorPnl.add(getMenuBar());
        }
        return this.selectorPnl;
    }

    /**
     * Selection panel.
     */
    private TPanel selectionPnl;

    /**
     * @return Selection panel.
     */
    private TPanel getSelectionPnl() {
        if (this.selectionPnl == null) {
            this.selectionPnl = new TPanel();
            this.selectionPnl.add(getSelectionLbl());
        }
        return this.selectionPnl;
    }

    /**
     * Selection label.
     */
    private JLabel selectionLbl;

    /**
     * @return Selection label.
     */
    private JLabel getSelectionLbl() {
        if (this.selectionLbl == null) {
            this.selectionLbl = new JLabel();
        }
        return this.selectionLbl;
    }

}
